/*
 * MIT License
 *
 * Copyright (c) 2023 北京凯特伟业科技有限公司
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */
package com.je.bpm.engine.impl.bpmn.behavior;

import com.je.bpm.core.model.CancelEventDefinition;
import com.je.bpm.core.model.FlowElement;
import com.je.bpm.core.model.event.BoundaryEvent;
import com.je.bpm.core.model.process.SubProcess;
import com.je.bpm.engine.ActivitiException;
import com.je.bpm.engine.delegate.DelegateExecution;
import com.je.bpm.engine.history.DeleteReason;
import com.je.bpm.engine.impl.bpmn.helper.ScopeUtil;
import com.je.bpm.engine.impl.context.Context;
import com.je.bpm.engine.impl.interceptor.CommandContext;
import com.je.bpm.engine.impl.persistence.entity.ExecutionEntity;
import com.je.bpm.engine.impl.persistence.entity.ExecutionEntityManager;
import com.je.bpm.engine.impl.util.CollectionUtil;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

public class CancelEndEventActivityBehavior extends FlowNodeActivityBehavior {

    private static final long serialVersionUID = 1L;

    @Override
    public void execute(DelegateExecution execution) {
        ExecutionEntity executionEntity = (ExecutionEntity) execution;
        CommandContext commandContext = Context.getCommandContext();
        ExecutionEntityManager executionEntityManager = commandContext.getExecutionEntityManager();

        // find cancel boundary event:
        ExecutionEntity parentScopeExecution = null;
        ExecutionEntity currentlyExaminedExecution = executionEntityManager.findById(executionEntity.getParentId());
        while (currentlyExaminedExecution != null && parentScopeExecution == null) {
            if (currentlyExaminedExecution.getCurrentFlowElement() instanceof SubProcess) {
                parentScopeExecution = currentlyExaminedExecution;
                SubProcess subProcess = (SubProcess) currentlyExaminedExecution.getCurrentFlowElement();
                if (subProcess.getLoopCharacteristics() != null) {
                    ExecutionEntity miExecution = parentScopeExecution.getParent();
                    FlowElement miElement = miExecution.getCurrentFlowElement();
                    if (miElement != null && miElement.getId().equals(subProcess.getId())) {
                        parentScopeExecution = miExecution;
                    }
                }

            } else {
                currentlyExaminedExecution = executionEntityManager.findById(currentlyExaminedExecution.getParentId());
            }
        }

        if (parentScopeExecution == null) {
            throw new ActivitiException("No sub process execution found for cancel end event " + executionEntity.getCurrentActivityId());
        }

        SubProcess subProcess = (SubProcess) parentScopeExecution.getCurrentFlowElement();
        BoundaryEvent cancelBoundaryEvent = null;
        if (CollectionUtil.isNotEmpty(subProcess.getBoundaryEvents())) {
            for (BoundaryEvent boundaryEvent : subProcess.getBoundaryEvents()) {
                if (CollectionUtil.isNotEmpty(boundaryEvent.getEventDefinitions()) &&
                        boundaryEvent.getEventDefinitions().get(0) instanceof CancelEventDefinition) {

                    cancelBoundaryEvent = boundaryEvent;
                    break;
                }
            }
        }

        if (cancelBoundaryEvent == null) {
            throw new ActivitiException("Could not find cancel boundary event for cancel end event " + executionEntity.getCurrentActivityId());
        }

        ExecutionEntity newParentScopeExecution = null;
        currentlyExaminedExecution = executionEntityManager.findById(parentScopeExecution.getParentId());
        while (currentlyExaminedExecution != null && newParentScopeExecution == null) {
            if (currentlyExaminedExecution.isScope()) {
                newParentScopeExecution = currentlyExaminedExecution;
            } else {
                currentlyExaminedExecution = executionEntityManager.findById(currentlyExaminedExecution.getParentId());
            }
        }

        if (newParentScopeExecution == null) {
            throw new ActivitiException("Programmatic error: no parent scope execution found for boundary event " + cancelBoundaryEvent.getId());
        }

        ScopeUtil.createCopyOfSubProcessExecutionForCompensation(parentScopeExecution);

        if (subProcess.getLoopCharacteristics() != null) {
            List<? extends ExecutionEntity> multiInstanceExecutions = parentScopeExecution.getExecutions();
            List<ExecutionEntity> executionsToDelete = new ArrayList<ExecutionEntity>();
            for (ExecutionEntity multiInstanceExecution : multiInstanceExecutions) {
                if (!multiInstanceExecution.getId().equals(parentScopeExecution.getId())) {
                    ScopeUtil.createCopyOfSubProcessExecutionForCompensation(multiInstanceExecution);

                    // end all executions in the scope of the transaction
                    executionsToDelete.add(multiInstanceExecution);
                    deleteChildExecutions(multiInstanceExecution, executionEntity, commandContext, DeleteReason.TRANSACTION_CANCELED);

                }
            }

            for (ExecutionEntity executionEntityToDelete : executionsToDelete) {
                deleteChildExecutions(executionEntityToDelete, executionEntity, commandContext, DeleteReason.TRANSACTION_CANCELED);
            }
        }

        // The current activity is finished (and will not be ended in the deleteChildExecutions)
        commandContext.getHistoryManager().recordActivityEnd(executionEntity, null);

        // set new parent for boundary event execution
        executionEntity.setParent(newParentScopeExecution);
        executionEntity.setCurrentFlowElement(cancelBoundaryEvent);

        // end all executions in the scope of the transaction
        deleteChildExecutions(parentScopeExecution, executionEntity, commandContext, DeleteReason.TRANSACTION_CANCELED);
        commandContext.getHistoryManager().recordActivityEnd(parentScopeExecution, DeleteReason.TRANSACTION_CANCELED);

        Context.getAgenda().planTriggerExecutionOperation(executionEntity);
    }

    protected void deleteChildExecutions(ExecutionEntity parentExecution, ExecutionEntity notToDeleteExecution,
                                         CommandContext commandContext, String deleteReason) {
        // Delete all child executions
        ExecutionEntityManager executionEntityManager = commandContext.getExecutionEntityManager();
        Collection<ExecutionEntity> childExecutions = executionEntityManager.findChildExecutionsByParentExecutionId(parentExecution.getId());
        if (CollectionUtil.isNotEmpty(childExecutions)) {
            for (ExecutionEntity childExecution : childExecutions) {
                if (!(childExecution.getId().equals(notToDeleteExecution.getId()))) {
                    deleteChildExecutions(childExecution, notToDeleteExecution, commandContext, deleteReason);
                }
            }
        }

        executionEntityManager.deleteExecutionAndRelatedData(parentExecution, deleteReason);
    }

}
